// 平行光
// Created by denglibin on 2021/4/7.
//


#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.h"
#include "window.h"
#include "gl_object.h"
#include "cglm.h"
#include "camera.h"
// settings
static const  int width = 800;
static const  int height = 600;
//顶点坐标
static float vertices[] = {
        // 顶点                // 法向量           // 纹理坐标
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
        0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
};
//10个立方体的位置
static vec3 cubePositions[10] = {
        {0.0f,  0.0f,  0.0f},
        {2.0f,  5.0f, -15.0f},
        {-1.5f, -2.2f, -2.5f},
        {-3.8f, -2.0f, -12.3f},
        {2.4f, -0.4f, -3.5f},

        {-1.7f,  3.0f, -7.5f},
        { 1.3f, -2.0f, -2.5f},
        {1.5f,  2.0f, -2.5f},
        {1.5f,  0.2f, -1.5f},
        {-1.3f,  1.0f, -1.5f}
};

static GLFWwindow *window;
static Camera *camera;
static float angle =0;
static vec3 objectColor = {1.0f, 0.5f, 0.31f}; //物体颜色
static vec3 lightColor = { 1.0f, 1.0f, 1.0f}; //灯颜色
//鼠标回调,改变摄像机的方向
static void mouse_callback(GLFWwindow* window, double x, double y);
//滚轮回调,当滚动鼠标滚轮的时候，y_offset值代表我们竖直滚动的大小
//当scroll_callback函数被调用后，我们改变全局变量fov变量的内容。因为45.0f是默认的视野值，我们将会把缩放级别(Zoom Level)限制在1.0f到45.0f。
static void scroll_callback(GLFWwindow* window, double x_offset, double y_offset);

/**
 * 输入事件监听，改变摄像机的位置
 */
static void processInput1(GLFWwindow *w)
{
    if (glfwGetKey(w, GLFW_KEY_ESCAPE) == GLFW_PRESS){
        glfwSetWindowShouldClose(w, 1);
    }
    //按键事件交给相机处理
    pressKeyMove(camera, w);

}

/**
 * 清除资源
 */
static void clean(){
    //glfwTerminate会销毁窗口释放资源，因此在调用该函数后，如果想使用glfw库函数，就必须重新初始化。
    glfwTerminate();
    free(camera);
}
/**
 * 初始化
 */
static void init(){
    //初始化，创建窗口
    window = initWindow(width, height, "LearnOpenGL");
    camera = initCamera();//创建相机

    //调用这个函数之后，无论我们怎么去移动鼠标，光标都不会显示了，它也不会离开窗口。对于FPS摄像机系统来说非常完美。
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    //注册鼠标回调函数
    glfwSetCursorPosCallback(window, mouse_callback);
    //注册鼠标滚轮的回调函数
    glfwSetScrollCallback(window, scroll_callback);
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
}
/**
 * 设置链接顶点属性
 */
static void setVertexAttr()
{
    //顶点位置
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    //法向量
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*) (3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    //纹理
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*) (6 * sizeof(float)));
    glEnableVertexAttribArray(2);

}
/**
 * 变换（平移,旋转）模型矩阵
 * @param shaderProgram
 * @param angel 旋转角度
 * @param rotateV 旋转轴向量
 * @param translateV 平移向量
 */
static void modelMat(GLuint shaderProgram, float angel, vec3 rotateV, vec3 translateV){
    mat4 model = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵

    glm_translate(model, translateV);//平移变换
    glm_rotate(model, angel/180.0f * CGLM_PI, rotateV);//旋转

    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "objectModel");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) model);
}
/**
 * 观察矩阵
 * 观察空间经常被人们称之OpenGL的摄像机(Camera)（所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space)）。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。
 * 而这通常是由一系列的位移和旋转的组合来完成，平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里，它被用来将世界坐标变换到观察空间
 * @param shaderProgram
 */
static void viewMat(GLuint shaderProgram){

    mat4 view = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    vec3 target = GLM_VEC3_ZERO_INIT;
    //摄像机位置 + 摄像机方向 = 目标位置
    glm_vec3_add(camera->cameraPos, camera->cameraFront, target);
    //LookAt函数需要一个位置、目标和上向量
    glm_lookat(camera->cameraPos, target, camera->cameraUp, view);
    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "view");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) view);

}

/**
 * 投影矩阵 使用透视投影
 * @param shaderProgram
 */
static void projectionMat(GLuint shaderProgram){
    mat4 projection = GLM_MAT4_IDENTITY_INIT; //定义一个变换矩阵 单位矩阵
    glm_perspective(camera->fovy, (float )width/(float )height, 0.1f, 100.0f, projection);
    unsigned int transformLoc = glGetUniformLocation(shaderProgram, "projection");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, (const GLfloat *) projection);

}

/**
 * 设置物体uniform颜色变量
 * @param shaderProgram
 */
static void setUniform(GLuint shaderProgram){


    //材质变量
    vec3  material_ambient = {1.0f, 0.5f, 0.31f}; //在环境光照下这个物体反射得是什么颜色，通常这是和物体颜色相同的颜色
    vec3  material_diffuse = {1.0f, 0.5f, 0.31f}; //在漫反射光照下物体的颜色。（和环境光照一样）漫反射颜色也要设置为我们需要的物体颜色
    vec3  material_specular = {0.5f, 0.5f, 0.5f}; //镜面光照对物体的颜色影响
    float  material_shininess= 32.0f; //影响镜面高光的散射/半径

    //光源变量
    vec3  light_ambient = {0.2f, 0.2f, 0.2f}; //环境光照通常会设置为一个比较低的强度，因为我们不希望环境光颜色太过显眼
    vec3  light_diffuse = {0.5f, 0.5f, 0.5f}; //漫反射分量通常设置为光所具有的颜色，通常是一个比较明亮的白色; 将光照调暗了一些以搭配场景
    vec3  light_specular = {1.0f, 1.0f, 1.0f}; //镜面光分量通常会保持为vec3(1.0)，以最大强度发光
    vec3  light_direction = {-1.0f, 0.0f, 0.0f}; //光方向 x轴负方向（从右往左）



    setUniformVec3(shaderProgram, "lightColor", lightColor);
    setUniformVec3(shaderProgram, "objectColor", objectColor);
    setUniformVec3(shaderProgram, "viewPos", camera->cameraPos);

    //设置材质变量
    setUniformVec3(shaderProgram, "material.ambient",  material_ambient);
    setUniformFloat(shaderProgram, "material.shininess", material_shininess);

    //设置光源变量
    setUniformVec3(shaderProgram, "light.ambient",  light_ambient);
    setUniformVec3(shaderProgram, "light.diffuse",  light_diffuse);
    setUniformVec3(shaderProgram, "light.specular", light_specular);
    setUniformVec3(shaderProgram, "light.direction", light_direction);
    setUniformVec3(shaderProgram, "light.lightColor", lightColor);

    glUniform1i(glGetUniformLocation(shaderProgram, "material.diffuse"), 0);
    glUniform1i(glGetUniformLocation(shaderProgram, "material.specular"), 1);

}






int main2_5(void)
{
    init();
    //编译着色器源码，创建着色器程序
    GLuint objectShaderProgram = compileShaderSource("D:\\CLionProjects\\opengl-mingw\\shader2\\shader5_object.vs",
                                                     "D:\\CLionProjects\\opengl-mingw\\shader2\\shader5_object.fs");
    GLuint VBO = createVBO(vertices, sizeof (vertices) / sizeof (float ));
    //创建物体的VAO
    GLuint objectVAO = createVAO();
    setVertexAttr();
    //创建纹理
    GLuint texture = createTexture("D:\\CLionProjects\\opengl-mingw\\img\\container2.png", GL_RGBA, GL_RGBA);
    //一般当你打算绘制多个物体时，你首先要生成/配置所有的VAO（和必须的VBO及属性指针)，然后储存它们供后面使用。当我们打算绘制物体的时候就拿出相应的VAO，绑定它，绘制完物体后，再解绑VAO。
    while (!glfwWindowShouldClose(window))
    {
        processInput1(window);
        //设置清除颜色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        //使用上面设置的颜色清空屏幕
        // glClear(GL_COLOR_BUFFER_BIT);
        //因为使用了深度测试，我们也想要在每次渲染迭代之前清除深度缓冲（否则前一帧的深度信息仍然保存在缓冲中）。就像清除颜色缓冲一样
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        //调用glUseProgram函数，用刚创建的程序对象作为它的参数，以激活这个程序对象
        glUseProgram(objectShaderProgram);


        setUniform(objectShaderProgram);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture);

        //设置模型观察和投影矩阵
        viewMat(objectShaderProgram);
        projectionMat(objectShaderProgram);
        //绑定物体VAO
        glBindVertexArray(objectVAO);
        vec3 rotateV = {1.0f, 0.3f, 0.5f};
        //画10次
        for(int i = 0; i < 10; i++){
            //设置平移位置和旋转角度
            modelMat(objectShaderProgram, (float )i * 20.0f * (float )(i+1), rotateV, cubePositions[i]);

            glDrawArrays(GL_TRIANGLES, 0, 36);
        }
        glBindVertexArray(0);

        //交换颜色缓冲区
        glfwSwapBuffers(window);
        //检查事件
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &objectVAO);
    glDeleteBuffers(1, &VBO);
    clean();
    return 0;
}

//鼠标回调
static void mouse_callback(GLFWwindow* win, double x, double y){
    //改变摄像机的方向
    mouseDirection(camera, win, x, y);
}
//滚轮回调
static void scroll_callback(GLFWwindow* win, double x_offset, double y_offset)
{
    //改变视角
    scrollFov(camera, win, x_offset, y_offset);
}